<?php

namespace PHPSpring\Utils;

use PHPSpring\Application;
use PHPSpring\Core\RuntimeAnnotation;
use Illuminate\Support\Facades\DB;

class Scanner {

  private static array $annos = [];

  /**
   * @param string $path 要扫描注解的路径
   * @param string|null $projectDir 缓存目录，为null时不缓存到文件，只返回
   * @param string $classPrefix 类命名空间前缀
   * @return array
   */
  public static function scan($scanPath, $projectDir, $classPrefix = '') {
    RuntimeAnnotation::init($projectDir);
    File::readDir(
      $scanPath
      , function ($path, $file) use ($classPrefix) {
          $clzName = substr($file, 0, -4);
          $clz     = $clzName;
          if (!empty($path)) {
            $clz = $path . DIRECTORY_SEPARATOR . $clz;
          }
          $clz = str_replace(DIRECTORY_SEPARATOR, '\\', $clz);
          if (!empty($classPrefix)) {
            $clz = $classPrefix . '\\' . $clz;
          }
          $annos = AnnotationParser::parse($clz);
          if (!empty($annos)) {
            self::$annos[strtolower($clzName)] = $annos;
          }
        }
      , '/\.php$/i');

    if (!empty($projectDir)) {
      $cacheFile = RuntimeAnnotation::$cacheDir . DIRECTORY_SEPARATOR . 'annos.php';
      $content   = "<?php\nreturn ".var_export(self::$annos, true).";\n";
      file_put_contents($cacheFile, $content);
    }

    return self::$annos;
  }

  public static function entities (): array {
    $allTables = [];
    $tables    = DB::select('SHOW TABLES');
    $database  = Application::config('database.connections.mysql.database');
    if (empty($database)) {
      throw new \InvalidArgumentException('Could not obtain database name.');
    }

    $key = "Tables_in_{$database}";

    $tableComment = DB::select("SELECT TABLE_NAME, TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='{$database}'");

    foreach ($tables as $index => $table) {
      if (empty($table->$key) || !is_string($table->$key)) {
        continue;
      }

      $desc = DB::select('SHOW FULL COLUMNS FROM `'.$table->$key.'`');

      $comment = '';
      foreach ($tableComment as $eachComment) {
        if ($eachComment->TABLE_NAME == $table->$key) {
          $comment = $eachComment->TABLE_COMMENT;
        }
      }

      $entityClassName = Utils::fieldToVar($table->$key, true);

      [$namespace, $template] = call_user_func([self::class, 'genEntity'], $entityClassName, $desc, $comment);
      $dir = Application::appPath("/Entity{$namespace}");
      if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
      }

      $destPhpFile = "{$dir}/{$entityClassName}.php";

      if (file_exists($destPhpFile)) {
        $content = file_get_contents($destPhpFile);
        if (!mb_strpos($content, '//AUTO GENERATED')) {
          echo "NOT Overwrite! PATH: {$destPhpFile}, Delete it first!".PHP_EOL;
          echo 'Preview:'.PHP_EOL.'=============================================='.PHP_EOL.$template.'=============================================='.PHP_EOL;
          continue;
        }
      }

      $ok = file_put_contents($destPhpFile, $template);
      echo "[{$index}]:\t{$destPhpFile} - ".($ok ? '√' : '×').PHP_EOL;
    }

    return $allTables;
  }


  protected static function genEntity ($entityClassName, $desc, $comment): array {
    $namespaceSlot = '';
    $namespace     = '';
    if (mb_strpos($comment, '(') === 0) {
      $firstEnd = mb_strpos($comment, ')', 2);
      if (!empty($firstEnd)) {
        $namespace     = mb_substr($comment, 1, $firstEnd - 1);
        $namespaceSlot = "\\{$namespace}";
      }
    }

    $fields = [];
    foreach ($desc as $field) {
      $fieldName = Utils::fieldToVar($field->Field);

      $nullableSlot = '';
      if ($field->Null == 'YES') {
        $nullableSlot = '?';
      }

      $typeSlot = '';
      $type     = 'mixed';
      if (strpos($field->Comment, 'JSON')) {
        $typeSlot = " ?\\PHPSpring\\Type\\Json";
        $type     = 'json';
      } elseif (stripos($field->Type, 'int') !== false) {
        $typeSlot = " {$nullableSlot}int";
        $type     = 'int';
      } elseif ((stripos($field->Type, 'float') !== false) || (stripos($field->Type, 'double') !== false) || (stripos($field->Type, 'decimal') !== false)) {
        $typeSlot = " {$nullableSlot}float";
        $type     = 'float';
      } elseif (stripos($field->Type, 'datetime') !== false) {
        $typeSlot = " ?\\PHPSpring\\Type\\Datetime";//始终可null
        $type     = 'datetime';
      } elseif ((stripos($field->Type, 'text') !== false) || (stripos($field->Type, 'char') !== false)) {
        $typeSlot = " {$nullableSlot}string";
        $type     = 'string';
      }

      $defaultSlot = '';
      if (is_string($field->Default)) {
        if (in_array($type, ['datetime', 'json'])) {
          $defaultSlot = " = null";
        } elseif ($type == 'int') {
          $defaultSlot = " = ".(int)$field->Default;
        } elseif ($type == 'float') {
          $defaultSlot = " = ".(float)$field->Default;
        } else {
          $defaultSlot = " = '$field->Default'";
        }
      } elseif (is_numeric($field->Default)) {
        $defaultSlot = " = $field->Default";
      }

      $fieldComment = $field->Comment;
      $fieldCommentSlot = '';
      if (!empty($fieldComment) && is_string($fieldComment)) {
        $fieldCommentSlot = "{$fieldComment}";
      }

      if ($field->Key == 'PRI') {
        if ($field->Extra == 'auto_increment') {
          $fieldDoc = "    /** {$fieldCommentSlot}@ID(\PHPSpring\Type\IDType::AUTO) */".PHP_EOL;
        } else {
          $fieldDoc = "    /** @ID(\PHPSpring\Type\IDType::INPUT) */".PHP_EOL;
        }
      } else {
        $fieldDoc = "    /** {$fieldCommentSlot} */".PHP_EOL;
      }

      $fields[] = "{$fieldDoc}    public{$typeSlot} \${$fieldName}{$defaultSlot};";
    }

    $fieldsSlot = implode(PHP_EOL, $fields);
    $template   = <<<CLS
<?php
//AUTO GENERATED: remove this line to prevent overwriting next generation.(移除该行防止下次生成时被覆盖)

namespace App\Entity{$namespaceSlot};

/**
 * {$comment}
 * @Entity
 */
class {$entityClassName}
{

{$fieldsSlot}

}

CLS;

    return [
      "/{$namespace}",
      $template,
    ];
  }

}
